//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package com.saasovation.common.domain.model.process;

import java.util.Date;

import com.saasovation.common.domain.model.Entity;

/**
 * @实体：抽象流程
 */
public abstract class AbstractProcess extends Entity implements Process {

    private static final long serialVersionUID = 1L;
    /**
     * 允许的持续时间
     */
    private long allowableDuration;
    /**
     * 并发版本
     */
    private int concurrencyVersion;
    /**
     * 内容描述
     */
    private String description;
    /**
     * 流程ID
     */
    private ProcessId processId;
    /**
     * 流程完成类型
     */
    private ProcessCompletionType processCompletionType;
    /**
     * 开始日期
     */
    private Date startTime;
    /**
     * 租户ID
     */
    private String tenantId;
    /**
     * 超时日期
     */
    private Date timedOutDate;
    /**
     * 允许的总重试次数
     */
    private int totalRetriesPermitted;

    /**
     * 由以下参数构造抽象流程
     * @param aTenantId                 租户ID
     * @param aProcessId                流程ID
     * @param aDescription              内容描述
     */
    public AbstractProcess(
            String aTenantId,
            ProcessId aProcessId,
            String aDescription) {

        super();

        this.setDescription(aDescription);
        // 设置流程完成类型为：未完成
        this.setProcessCompletionType(ProcessCompletionType.NotCompleted);
        this.setProcessId(aProcessId);
        this.setStartTime(new Date());
        this.setTenantId(aTenantId);
    }

    /**
     * 由以下参数构造抽象流程
     * @param aTenantId                 租户ID
     * @param aProcessId                流程ID
     * @param aDescription              内容描述
     * @param anAllowableDuration       允许的持续时间
     */
    public AbstractProcess(
            String aTenantId,
            ProcessId aProcessId,
            String aDescription,
            long anAllowableDuration) {

        // 调用合适构造器
        this(aTenantId, aProcessId, aDescription);
        // 设置允许的持续时间
        this.setAllowableDuration(anAllowableDuration);
    }

    /**
     * 由以下参数构造抽象流程
     * @param aTenantId                 租户ID
     * @param aProcessId                流程ID
     * @param aDescription              内容描述
     * @param anAllowableDuration       允许的持续时间
     * @param aTotalRetriesPermitted    允许的总重试次数
     */
    public AbstractProcess(
            String aTenantId,
            ProcessId aProcessId,
            String aDescription,
            long anAllowableDuration,
            int aTotalRetriesPermitted) {
        // 调用合适构造器
        this(aTenantId, aProcessId, aDescription, anAllowableDuration);
        // 设置允许的总重试次数
        this.setTotalRetriesPermitted(aTotalRetriesPermitted);
    }

    /**
     * 获取允许的持续时间
     * @return  返回长整型
     */
    @Override
    public long allowableDuration() {
        return this.allowableDuration;
    }

    /**
     * 能否超时
     * @return 返回布尔
     */
    @Override
    public boolean canTimeout() {
        return this.allowableDuration() > 0;
    }

    /**
     * 获取当前持续时间
     * @return 返回长整型
     */
    @Override
    public long currentDuration() {
        return this.calculateTotalCurrentDuration(new Date());
    }

    /**
     * 获取内容描述
     * @return 返回字符串
     */
    @Override
    public String description() {
        return this.description;
    }

    /**
     * 流程是否完成
     * @return  返回布尔
     */
    @Override
    public boolean didProcessingComplete() {
        return this.isCompleted() && !this.isTimedOut();
    }

    /**
     * 当并发冲突时失败
     * @param aVersion  版本
     */
    public void failWhenConcurrencyViolation(int aVersion) {
        this.assertStateTrue(
                aVersion == this.concurrencyVersion(),
                "Concurrency Violation: Stale data detected. Entity was already modified.");
    }

    /**
     * 通知超时
     * @param aTimedOutDate 超时日期
     */
    @Override
    public void informTimeout(Date aTimedOutDate) {
        this.assertStateTrue(
                this.hasProcessTimedOut(aTimedOutDate),
                "The date " + aTimedOutDate + " does not indicate a valid timeout.");

        this.setProcessCompletionType(ProcessCompletionType.TimedOut);
        this.setTimedOutDate(aTimedOutDate);
    }

    /**
     * 是否已完成
     * @return 返回布尔
     */
    @Override
    public boolean isCompleted() {
        return !this.notCompleted();
    }

    /**
     * 是否超时
     * @return  返回布尔
     */
    @Override
    public boolean isTimedOut() {
        return this.timedOutDate() != null;
    }

    /**
     * 是否没完成
     * @return  返回布尔
     */
    @Override
    public boolean notCompleted() {
        return this.processCompletionType().equals(ProcessCompletionType.NotCompleted);
    }

    /**
     * 获取流程完成类型
     * @return  返回流程完成类型
     */
    @Override
    public ProcessCompletionType processCompletionType() {
        return this.processCompletionType;
    }

    /**
     * 获取流程ID
     * @return  返回流程ID
     */
    @Override
    public ProcessId processId() {
        return this.processId;
    }

    /**
     * 获取开始日期
     * @return  返回日期
     */
    @Override
    public Date startTime() {
        return this.startTime;
    }

    /**
     * 获取租户ID
     * @return  返回字符串
     */
    public String tenantId() {
        return this.tenantId;
    }

    /**
     * 获取时间受限的流程跟踪器
     * @return  返回时间受限的流程跟踪器
     */
    @Override
    public TimeConstrainedProcessTracker timeConstrainedProcessTracker() {
        this.assertStateTrue(this.canTimeout(), "Process does not timeout.");

        TimeConstrainedProcessTracker tracker =
                new TimeConstrainedProcessTracker(
                        this.tenantId(),
                        this.processId(),
                        this.description(),
                        this.startTime(),
                        this.allowableDuration(),
                        this.totalRetriesPermitted(),
                        this.processTimedOutEventType().getName());

        return tracker;
    }

    /**
     * 超时日期
     * @return  返回日期
     */
    @Override
    public Date timedOutDate() {
        return this.timedOutDate;
    }

    /**
     * 获取总允许持续时间
     * @return  返回长整型
     */
    @Override
    public long totalAllowableDuration() {
        // 总允许持续时间
        long totalAllowableDuration = this.allowableDuration();
        // 允许的总重试次数
        long totalRetriesPermitted = this.totalRetriesPermitted();

        // 允许的总重试次数 > 0 时，乘以允许的总重试次数
        if (totalRetriesPermitted > 0) {
            totalAllowableDuration *= totalRetriesPermitted;
        }

        return totalAllowableDuration;
    }

    /**
     * 获取允许的总重试次数
     * @return 返回整型
     */
    @Override
    public int totalRetriesPermitted() {
        return this.totalRetriesPermitted;
    }

    @Override
    public boolean equals(Object anObject) {
        boolean equalObjects = false;

        if (anObject != null && this.getClass() == anObject.getClass()) {
            AbstractProcess typedObject = (AbstractProcess) anObject;
            equalObjects =
                this.tenantId().equals(typedObject.tenantId()) &&
                this.processId().equals(typedObject.processId());
        }

        return equalObjects;
    }

    @Override
    public int hashCode() {
        int hashCodeValue =
            + (71547 * 953)
            + this.tenantId().hashCode()
            + this.processId().hashCode();

        return hashCodeValue;
    }

    @Override
    public String toString() {
        return "AbstractProcess [id=" + id() + "allowableDuration=" + allowableDuration
                + ", description=" + description + ", processId=" + processId
                + ", processCompletionType=" + processCompletionType + ", startTime=" + startTime
                + ", tenantId=" + tenantId + ", timedOutDate=" + timedOutDate
                + ", totalRetriesPermitted=" + totalRetriesPermitted + "]";
    }

    /**
     * 默认构造器
     */
    protected AbstractProcess() {
        super();
    }

    /**
     * 完成流程
     * @param aProcessCompletionType    流程完成类型
     */
    protected void completeProcess(ProcessCompletionType aProcessCompletionType) {
        if (!this.isCompleted() && this.completenessVerified()) {
            this.setProcessCompletionType(aProcessCompletionType);
        }
    }

    /**
     * 获取并发版本号
     * @return  返回整型
     */
    protected int concurrencyVersion() {
        return this.concurrencyVersion;
    }

    /**
     * 设置并发版本
     * @param aConcurrencyVersion 并发版本号
     */
    protected void setConcurrencyVersion(int aConcurrencyVersion) {
        this.concurrencyVersion = aConcurrencyVersion;
    }

    /**
     * 完整性验证
     * @return 返回布尔
     */
    protected abstract boolean completenessVerified();

    /**
     * 获取流程超时事件类型
     * @return 返回继承自流程超时
     */
    protected abstract Class<? extends ProcessTimedOut> processTimedOutEventType();

    /**
     * 计算总当前持续时间
     * @param aDateFollowingStartTime 开始时间之后的日期
     * @return 返回长整型
     */
    private long calculateTotalCurrentDuration(Date aDateFollowingStartTime) {
        return aDateFollowingStartTime.getTime() - this.startTime().getTime();
    }

    /**
     * 是否有流程超时
     * @param aTimedOutDate 流程超时
     * @return 返回布尔
     */
    private boolean hasProcessTimedOut(Date aTimedOutDate) {
        return this.calculateTotalCurrentDuration(aTimedOutDate) >=
               this.totalAllowableDuration();
    }

    /**
     * 设置允许的持续时间
     * @param anAllowableDuration 允许的持续时间
     */
    private void setAllowableDuration(long anAllowableDuration) {
        this.assertArgumentTrue(
                anAllowableDuration > 0,
                "The allowable duration must be greater than zero.");

        this.allowableDuration = anAllowableDuration;
    }

    /**
     * 设置内容描述
     * @param description   内容描述
     */
    private void setDescription(String description) {
        this.description = description;
    }

    /**
     * 设置流程完成类型
     * @param aProcessCompletionType    流程完成类型
     */
    private void setProcessCompletionType(ProcessCompletionType aProcessCompletionType) {
        this.processCompletionType = aProcessCompletionType;
    }

    /**
     * 设置流程ID
     * @param aProcessId    流程ID
     */
    private void setProcessId(ProcessId aProcessId) {
        this.assertArgumentNotNull(aProcessId, "Process id must be provided.");

        this.processId = aProcessId;
    }

    /**
     * 设置开始日期
     * @param aStartTime    开始日期
     */
    private void setStartTime(Date aStartTime) {
        this.startTime = aStartTime;
    }

    /**
     * 设置租户ID
     * @param aTenantId 租户ID
     */
    private void setTenantId(String aTenantId) {
        this.assertArgumentNotEmpty(aTenantId, "Tenant id must be provided.");

        this.tenantId = aTenantId;
    }

    /**
     * 设置超时日期
     * @param aTimedOutDate 超时日期
     */
    private void setTimedOutDate(Date aTimedOutDate) {
        this.timedOutDate = aTimedOutDate;
    }

    /**
     * 设置允许的总重试次数
     * @param aTotalRetriesPermitted 允许的总重试次数
     */
    private void setTotalRetriesPermitted(int aTotalRetriesPermitted) {
        this.totalRetriesPermitted = aTotalRetriesPermitted;
    }
}
